package code.google.jcontainer.support.inject;

import code.google.jcontainer.NoSuchComponentRuntimeException;
import code.google.jcontainer.resolver.FieldAnnotationResolver;
import code.google.jcontainer.resolver.FieldResolveContext;
import code.google.jcontainer.resolver.MethodAnnotationResolver;
import code.google.jcontainer.resolver.MethodResolveContext;
import code.google.jcontainer.resolver.ResolverException;
import code.google.jcontainer.util.MethodUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * \@Inject resolver, inject the dependency AFTER_CONSTRUCT
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class InjectAnnotationResolver implements FieldAnnotationResolver, MethodAnnotationResolver {

    public void resolveFieldAnnotationAfterConstruct(FieldResolveContext fieldResolveContext) throws ResolverException {
        Object component = fieldResolveContext.getComponent();
        Field field = fieldResolveContext.getField();
        Inject injectAnnotation = (Inject)fieldResolveContext.getAnnotation();
        String injectName = injectAnnotation.name().trim();
        Object refComponent = null;
        if(!injectName.equals("")) { // inject by name
            try {
                refComponent = fieldResolveContext.getComponent(injectName);
            }
            catch (NoSuchComponentRuntimeException e){
                throw new InjectRuntimeException("Could not inject component by class: " + field.getType(), e);
            }
        }
        else { // inject by componentClass
            refComponent = fieldResolveContext.getContainer().getComponent(field.getType());
            if(refComponent == null) {
                throw new InjectRuntimeException("Could not inject component by class: " + field.getType() + " in component: " + fieldResolveContext.getComponentName());
            }
        }
        try {
            field.setAccessible(true);
            field.set(component, refComponent);
        }
        catch (Exception e) {
            throw new ResolverException("Failed to inject " + refComponent + " to component: " + fieldResolveContext.getComponentName() + "by field:" + field, e);
        }
    }

    public void resolveMethodAnnotationAfterConstruct(MethodResolveContext methodResolveContext) throws ResolverException {
        Object component = methodResolveContext.getComponent();
        Method method = methodResolveContext.getMethod();

        if(!MethodUtils.isSetMethod(method)){
            throw new ResolverException("@Inject can not annotates on none set method: " + method);
        }

        Class<?> parameterType = method.getParameterTypes()[0];
        Inject injectAnnotation = (Inject)methodResolveContext.getAnnotation();
        String injectName = injectAnnotation.name().trim();
        Object refComponent = null;
        if(!injectName.equals("")) { // inject by name
            try {
                refComponent = methodResolveContext.getComponent(injectName);
            }
            catch (NoSuchComponentRuntimeException e){
                throw new InjectRuntimeException("Could not inject component by class: " + parameterType, e);
            }
        }
        else { // inject by componentClass
            refComponent = methodResolveContext.getContainer().getComponent(parameterType);
            if(refComponent == null) {
                throw new InjectRuntimeException("Could not inject component by class: " + parameterType + " in component: " + methodResolveContext.getComponentName());
            }
        }
        try {
            method.setAccessible(true);
            method.invoke(component, refComponent);
        }
        catch (Exception e) {
            throw new ResolverException("Failed to inject " + refComponent + " to component: " + methodResolveContext.getComponentName() + " by method:" + method, e);
        }

    }

    public void resolveMethodAnnotationBeforeMethod(MethodResolveContext methodResolveContext) throws ResolverException {
        // do nothing
    }

    public void resolveMethodAnnotationAfterMethod(MethodResolveContext methodResolveContext) throws ResolverException {
        // do nothing
    }

}
